home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 98 / Skunkware 98.iso / src / mail / pine3.96.tar.gz / pine3.96.tar / pine3.96 / pine / screen.c < prev    next >
C/C++ Source or Header  |  1996-05-07  |  40KB  |  1,377 lines

  1. #if !defined(lint) && !defined(DOS)
  2. static char rcsid[] = "$Id: screen.c,v 4.87 1996/05/08 01:05:18 mikes Exp $";
  3. #endif
  4. /*----------------------------------------------------------------------
  5.  
  6.             T H E    P I N E    M A I L   S Y S T E M
  7.  
  8.    Laurence Lundblade and Mike Seibel
  9.    Networks and Distributed Computing
  10.    Computing and Communications
  11.    University of Washington
  12.    Administration Builiding, AG-44
  13.    Seattle, Washington, 98195, USA
  14.    Internet: lgl@CAC.Washington.EDU
  15.              mikes@CAC.Washington.EDU
  16.  
  17.    Please address all bugs and comments to "pine-bugs@cac.washington.edu"
  18.  
  19.  
  20.    Pine and Pico are registered trademarks of the University of Washington.
  21.    No commercial use of these trademarks may be made without prior written
  22.    permission of the University of Washington.
  23.  
  24.    Pine, Pico, and Pilot software and its included text are Copyright
  25.    1989-1996 by the University of Washington.
  26.  
  27.    The full text of our legal notices is contained in the file called
  28.    CPYRIGHT, included with this distribution.
  29.  
  30.  
  31.    Pine is in part based on The Elm Mail System:
  32.     ***********************************************************************
  33.     *  The Elm Mail System  -  Revision: 2.13                             *
  34.     *                                                                     *
  35.     *             Copyright (c) 1986, 1987 Dave Taylor              *
  36.     *             Copyright (c) 1988, 1989 USENET Community Trust   *
  37.     ***********************************************************************
  38.  
  39.  
  40.   ----------------------------------------------------------------------*/
  41.  
  42. /*======================================================================
  43.        screen.c
  44.        Some functions for screen painting
  45.           - Painting and formatting of the key menu at bottom of screen
  46.           - Convert message status bits to string for display
  47.           - Painting and formatting of the titlebar line on top of the screen
  48.           - Updating of the titlebar line for changes in message number...
  49.   ====*/
  50.  
  51.  
  52. #include "headers.h"
  53.  
  54. /*
  55.  * Internal prototypes
  56.  */
  57. void  savebits PROTO((bitmap_t, bitmap_t, int));
  58. int   equalbits PROTO((bitmap_t, bitmap_t, int));
  59. char *percentage PROTO((long, long, int));
  60. void  output_keymenu PROTO((struct key_menu *, bitmap_t, int, int));
  61. int   digit_count PROTO((long));
  62. #ifdef    MOUSE
  63. void  print_inverted_label PROTO((int, MENUITEM *));
  64. #endif
  65.  
  66.  
  67. /* Saved key menu drawing state */
  68. static struct {
  69.     struct key_menu *km;
  70.     int              row,
  71.              column,
  72.                      blanked;
  73.     bitmap_t         bitmap;
  74. } km_state;
  75.  
  76.  
  77. /*
  78.  * Longest label that can be displayed in keymenu
  79.  */
  80. #define    MAX_LABEL    40
  81. #define MAX_KEYNAME     3
  82. static struct key last_time_buf[12];
  83. static int keymenu_is_dirty = 1;
  84.  
  85. void
  86. mark_keymenu_dirty()
  87. {
  88.     keymenu_is_dirty = 1;
  89. }
  90.  
  91.  
  92. /*
  93.  * Write an already formatted key_menu to the screen
  94.  *
  95.  * Args: km     -- key_menu structure
  96.  *       bm     -- bitmap, 0's mean don't draw this key
  97.  *       row    -- the row on the screen to begin on, negative values
  98.  *                 are counted from the bottom of the screen up
  99.  *       column -- column on the screen to begin on
  100.  *
  101.  * The bits in the bitmap are used from least significant to most significant,
  102.  * not left to right.  So, if you write out the bitmap in the normal way, for
  103.  * example,
  104.  * bm[0] = 0x5, bm[1] = 0x8, bm[2] = 0x21, bm[3] = bm[4] = bm[5] = 0
  105.  *   0000 0101    0000 1000    0010 0001   ...
  106.  * means that menu item 0 (first row, first column) is set, item 1 (2nd row,
  107.  * first column) is not set, item 2 is set, items 3-10 are not set, item 11
  108.  * (2nd row, 6th and last column) is set.  In the second menu (the second set
  109.  * of 12 bits) items 0-3 are unset, 4 is set, 5-8 unset, 9 set, 10-11 unset.
  110.  * That uses up bm[0] - bm[2].
  111.  * Just to make sure, here it is drawn out for the first set of 12 items in
  112.  * the first keymenu (0-11)
  113.  *    bm[0] x x x x  x x x x   bm[1] x x x x  x x x x
  114.  *          7 6 5 4  3 2 1 0                 1110 9 8
  115.  */
  116. void
  117. output_keymenu(km, bm, row, column)
  118. struct key_menu *km;
  119. bitmap_t     bm;
  120. int         row,
  121.          column;
  122. {
  123.     register struct key *k;
  124.     struct key          *last_time;
  125.     int                  i,
  126.              ufk,        /* using function keys */
  127.              real_row,
  128.              max_column, /* number of columns on screen */
  129.              off;        /* offset into keymap */
  130.     int                  j;
  131. #ifdef    MOUSE
  132.     char         keystr[MAX_KEYNAME + MAX_LABEL + 2];
  133.     extern void          register_key();
  134. #endif
  135.  
  136.     off          = km->which * 12;
  137.     max_column    = ps_global->ttyo->screen_cols;
  138.  
  139.     if(ps_global->ttyo->screen_rows < 4 || max_column <= 0){
  140.     keymenu_is_dirty = 1;
  141.     return;
  142.     }
  143.  
  144.     real_row = row > 0 ? row : ps_global->ttyo->screen_rows + row;
  145.  
  146.     if(keymenu_is_dirty){
  147.     ClearLines(real_row, real_row+1);
  148.     keymenu_is_dirty = 0;
  149.     /* first time through, set up storage */
  150.     if(!last_time_buf[0].name){
  151.         for(i = 0; i < 12; i++){
  152.         last_time = &last_time_buf[i];
  153.         last_time->name  = (char *)fs_get(MAX_KEYNAME + 1);
  154.         last_time->label = (char *)fs_get(MAX_LABEL + 1);
  155.         }
  156.     }
  157.  
  158.     for(i = 0; i < 12; i++)
  159.       last_time_buf[i].column = -1;
  160.     }
  161.  
  162.     for(i = 0; i < 12; i++){
  163.     int e;
  164.  
  165.     e = off + i;
  166.         dprint(9, (debugfile, "%2d %-7.7s %-10.10s %d\n", i,
  167.            km == NULL ? "(no km)" 
  168.            : km->keys[e].name == NULL ? "(null)" 
  169.            : km->keys[e].name,            
  170.            km == NULL ? "(no km)" 
  171.            : km->keys[e].label == NULL ? "(null)" 
  172.            : km->keys[e].label, km ? km->keys[e].column : 0));
  173. #ifdef    MOUSE
  174.     register_key(i, NO_OP_COMMAND, "", NULL, 0, 0, 0);
  175. #endif
  176.     }
  177.  
  178.     ufk = F_ON(F_USE_FK, ps_global);
  179.     dprint(9, (debugfile, "row: %d, real_row: %d, column: %d\n", row, 
  180.                real_row, column));
  181.  
  182.     for(i = 0; i < 2; i++){
  183.     int   c, el, empty, fkey, last_in_row;
  184.     short next_col;
  185.     char  temp[MAX_SCREEN_COLS+1];
  186.  
  187.     j = 6*i - 1;
  188.     if(i == 1)
  189.       max_column--;  /* some terminals scroll if you write in the
  190.                 lower right hand corner */
  191.  
  192.         for(c = 0, el = off+i, k = &km->keys[el];
  193.         k < &km->keys[off+12] && c < max_column;
  194.         k += 2, el += 2){
  195.  
  196.             if(k->column > max_column)
  197.               break;
  198.  
  199.         j++;
  200.             if(ufk)
  201.               fkey = 1 + k - &km->keys[off];
  202.  
  203.         empty     = (!bitnset(el,bm) || !k->name);
  204.         last_time = &last_time_buf[j];
  205.         if(k+2 < &km->keys[off+12]){
  206.             last_in_row = 0;
  207.         next_col    = last_time_buf[j+1].column;
  208.         }
  209.         else
  210.           last_in_row = 1;
  211.  
  212.         if(!(k->column == last_time->column
  213.          && (last_in_row || (k+2)->column <= next_col)
  214.          && ((empty && !*last_time->label && !*last_time->name)
  215.              || (k->label && !strcmp(k->label,last_time->label)
  216.              && ((k->name && !strcmp(k->name,last_time->name))
  217.                  || ufk))))){
  218.         if(empty){
  219.             /* blank out key with spaces */
  220.             strcpy(temp, repeat_char(
  221.                     ((last_in_row || (k+2)->column > max_column)
  222.                     ? max_column : (k+2)->column)
  223.                       - k->column, SPACE));
  224.             last_time->column  = k->column;
  225.             *last_time->name   = '\0';
  226.             *last_time->label  = '\0';
  227.             MoveCursor(real_row + i, column + k->column);
  228.             Write_to_screen(temp);
  229.             c = k->column + strlen(temp);
  230.         }
  231.         else{
  232.             /* short name of the key */
  233.             if(ufk)
  234.               sprintf(temp, "F%d", fkey);
  235.             else
  236.               strncpy(temp, k->name, MAX_KEYNAME);
  237.  
  238.             temp[MAX_KEYNAME] = '\0';
  239.             last_time->column = k->column;
  240.             strcpy(last_time->name, temp);
  241.             /* make sure name not too long */
  242. #ifdef    MOUSE
  243.             strcpy(keystr, temp);
  244. #endif
  245.             MoveCursor(real_row + i, column + k->column);
  246.             if(!empty)
  247.               StartInverse();
  248.  
  249.             Write_to_screen(temp);
  250.             c = k->column + strlen(temp);
  251.             if(!empty)
  252.               EndInverse();
  253.  
  254.             /* now the space after the name and the label */
  255.             temp[0] = '\0';
  256.             if(c < max_column){
  257.             strcpy(temp, " ");
  258.             strncat(temp, k->label, MAX_LABEL);
  259.             temp[max_column - c] = '\0';
  260.             c += strlen(temp);
  261.             }
  262.             
  263. #ifdef    MOUSE
  264.             strcat(keystr, temp);
  265. #endif
  266.             /* fill out rest of this key with spaces */
  267.             if(c < max_column){
  268.             if(last_in_row){
  269.                 strcat(temp, repeat_char(max_column - c, SPACE));
  270.                 c = max_column;
  271.             }
  272.             else{
  273.                 if(c < (k+2)->column){
  274.                 strcat(temp,
  275.                     repeat_char((k+2)->column - c, SPACE));
  276.                 c = (k+2)->column;
  277.                 }
  278.             }
  279.             }
  280.  
  281.             if(k->label)
  282.               strcpy(last_time->label, k->label);
  283.             else
  284.               *last_time->label = '\0';
  285.  
  286.             Write_to_screen(temp);
  287.         }
  288.         }
  289. #ifdef    MOUSE
  290.         else if(!empty)
  291.           /* fill in what register_key needs from cached data */
  292.           sprintf(keystr, "%s %s", last_time->name, last_time->label);
  293.  
  294.         if(!empty)
  295.           register_key(j, (ufk) ? PF1 + fkey - 1
  296.                     : (k->name[0] == '^')
  297.                     ? ctrl(k->name[1])
  298.                     : (!strucmp(k->name, "ret"))
  299.                         ? ctrl('M')
  300.                         : (!strucmp(k->name, "tab"))
  301.                         ? '\t'
  302.                         : (!strucmp(k->name, "spc"))
  303.                             ? ' '
  304.                             : k->name[0],
  305.                keystr, print_inverted_label,
  306.                real_row+i, k->column, strlen(keystr));
  307. #endif
  308.  
  309.         }
  310.  
  311.     while(++j < 6*(i+1))
  312.       last_time_buf[j].column = -1;
  313.     }
  314.  
  315.     fflush(stdout);
  316. }
  317.  
  318.  
  319. #ifdef    MOUSE
  320. /*
  321.  * print_inverted_label - highlight the label of the given menu item.
  322.  * (callback from pico mouse routines)
  323.  */
  324. void
  325. print_inverted_label(state, m)
  326.     int state;
  327.     MENUITEM *m;
  328. {
  329.     unsigned i, j;
  330.     int      col_offset;
  331.     char    *lp;
  332.  
  333.     /*
  334.      * Leave the command name bold
  335.      */
  336.     col_offset = (state || !(lp=strchr(m->label, ' '))) ? 0 : (lp - m->label);
  337.     MoveCursor((int)(m->tl.r), (int)(m->tl.c) + col_offset);
  338.     if(state)
  339.       StartInverse();
  340.     else
  341.       EndInverse();
  342.  
  343.     for(i = m->tl.r; i <= m->br.r; i++)
  344.       for(j = m->tl.c + col_offset; j <= m->br.c; j++)
  345.     if(i == m->lbl.r && j == m->lbl.c + col_offset && m->label){
  346.         lp = m->label + col_offset;        /* show label?? */
  347.         while(*lp && j++ < m->br.c)
  348.           Writechar((unsigned int)(*lp++), 0);
  349.  
  350.         continue;
  351.     }
  352.     else
  353.       Writechar(' ', 0);
  354.  
  355.     if(state)
  356.       EndInverse();            /* turn inverse back off */
  357.  
  358. }
  359. #endif    /* MOUSE */
  360.  
  361.  
  362. /*
  363.  * Clear the key menu lines.
  364.  */
  365. void
  366. blank_keymenu(row, column)
  367. int row, column;
  368. {
  369.     if(FOOTER_ROWS(ps_global) > 1){
  370.     km_state.blanked    = 1;
  371.     km_state.row        = row;
  372.     km_state.column     = column;
  373.     MoveCursor(row, column);
  374.     CleartoEOLN();
  375.     MoveCursor(row+1, column);
  376.     CleartoEOLN();
  377.     fflush(stdout);
  378.     }
  379. }
  380.  
  381.  
  382. static struct key cancel_keys[] = 
  383.      {{NULL,NULL,KS_NONE},            {"^C","Cancel",KS_NONE},
  384.       {NULL,NULL,KS_NONE},            {NULL,NULL,KS_NONE},
  385.       {NULL,NULL,KS_NONE},            {NULL,NULL,KS_NONE},
  386.       {NULL,NULL,KS_NONE},            {NULL,NULL,KS_NONE},
  387.       {NULL,NULL,KS_NONE},            {NULL,NULL,KS_NONE},
  388.       {NULL,NULL,KS_NONE},            {NULL,NULL,KS_NONE},
  389.       {NULL,NULL,KS_NONE},            {NULL,NULL,KS_NONE}};
  390. INST_KEY_MENU(cancel_keymenu, cancel_keys);
  391.  
  392. void
  393. draw_cancel_keymenu()
  394. {
  395.     bitmap_t   bitmap;
  396.  
  397.     setbitmap(bitmap);
  398.     draw_keymenu(&cancel_keymenu, bitmap, ps_global->ttyo->screen_cols,
  399.          1-FOOTER_ROWS(ps_global), 0, FirstMenu, 0);
  400. }
  401.  
  402.  
  403. void
  404. clearfooter(ps)
  405.     struct pine *ps;
  406. {
  407.     ClearLines(ps->ttyo->screen_rows - 3, ps->ttyo->screen_rows - 1);
  408.     mark_keymenu_dirty();
  409.     mark_status_unknown();
  410. }
  411.         
  412.  
  413. /*
  414.  * Calculate formatting for key menu at bottom of screen
  415.  *
  416.  * Args:  km    -- The key_menu structure to format
  417.  *        bm    -- Bitmap indicating which menu items should be displayed.  If
  418.  *           an item is NULL, that also means it shouldn't be displayed.
  419.  *           Sometimes the bitmap will be turned on in that case and just
  420.  *           rely on the NULL entry.
  421.  *        width -- the screen width to format it at
  422.  *      what  -- Used to indicate which of the possible sets of 12 keys
  423.  *           to show next
  424.  *      which -- Only used when what == AParticularTwelve, in which case
  425.  *           it indicates which particular twelve.
  426.  *
  427.  * If already formatted for this particular screen width, this set of twelve
  428.  * keys, and this part of the bitmap set this way, then return.
  429.  *
  430.  * The formatting results in the column field in the key_menu being
  431.  * filled in.  The column field is the column to start the label at, the
  432.  * name of the key; after that is the label for the key.  The basic idea
  433.  * is to line up the end of the names and beginning of the labels.  If
  434.  * the name is too long and shifting it left would run into previous
  435.  * label, then shift the whole menu right, or at least that entry of
  436.  * things following are short enough to fit back into the regular
  437.  * spacing.  This has to be calculated and not fixed so it can cope with
  438.  * screen resize.
  439.  *
  440.  * This seems to be working correctly now.  I'd like to take some time to
  441.  * try to figure out what the heck all the calculations are about.  It seems
  442.  * too complicated at first glance.
  443.  */
  444. void
  445. format_keymenu(km, bm, width, what, which)
  446. struct key_menu *km;
  447. bitmap_t     bm;
  448. int         width;
  449. OtherMenu     what;
  450. int         which;     /* only used if what == AParticularTwelve */
  451. {
  452.     short   spacing[6];
  453.     int     i, first, prev_end_top, prev_end_bot, ufk, top_column, bot_column;
  454.     int     off;  /* offset into keymenu */
  455.     struct key *keytop, *keybot, *prev_keytop, *prev_keybot;
  456.  
  457.     if(what == FirstMenu)
  458.       km->which = 0;
  459.     else if(what == NextTwelve)
  460.       km->which = (km->which + 1) % (unsigned int)km->how_many;
  461.     else if(what == AParticularTwelve)
  462.       km->which = which;
  463.     /* else SameTwelve */
  464.  
  465.     /* it's already formatted. */
  466.     if(width == km->width[km->which] && equalbits(bm, km->bitmap, km->which))
  467.       return;
  468.  
  469.     /*
  470.      * If we're in the initial command sequence we may be using function
  471.      * keys instead of alphas, or vice versa, so we want to recalculate
  472.      * the formatting next time through.
  473.      */
  474.     if((F_ON(F_USE_FK,ps_global) && ps_global->orig_use_fkeys) ||
  475.         (F_OFF(F_USE_FK,ps_global) && !ps_global->orig_use_fkeys)){
  476.     km->width[km->which] = width;
  477.     savebits(km->bitmap, bm, km->which);
  478.     }
  479.  
  480.     off = km->which * 12;        /* off is offset into keymenu */
  481.     ufk = F_ON(F_USE_FK,ps_global);    /* ufk stands for using function keys */
  482.  
  483.     /* this spacing is column numbers for column key name ends in */
  484.     /*       F1 Menu       */
  485.     /*        ^            */
  486.     spacing[0] = 0;
  487.     for(i = 1; i < 6; i++) 
  488.       spacing[i] = spacing[i-1] + (width == 80 ? (i > 2 ? 14 : 13) : width/6);
  489.  
  490.     km->keys[off].column = km->keys[off+6].column = 0;
  491.     if(ufk)
  492.       first = 2;
  493.     else
  494.       first = max((bitnset(off,bm) && km->keys[off].name != NULL) ?
  495.             strlen(km->keys[off].name) : 0,
  496.             (bitnset(off+1,bm) && km->keys[off+1].name != NULL) ?
  497.             strlen(km->keys[off+1].name) : 0);
  498.  
  499.     prev_keytop = &km->keys[off];
  500.     prev_keybot = &km->keys[off+1];
  501.     for(i = 1; i < 6; i++){
  502.     int k_top, k_bot;
  503.  
  504.     k_top  = off + i*2;
  505.     k_bot  = k_top + 1;
  506.         keytop = &km->keys[k_top];
  507.         keybot = &km->keys[k_bot];
  508.  
  509.     prev_end_top = 
  510.          prev_keytop->column +
  511.          ((!bitnset(k_top-2,bm) || prev_keytop->name == NULL) ? 0 : 
  512.            (ufk ? (i-1 >= 5 ? 3 : 2) : strlen(prev_keytop->name))) +
  513.          1 + ((!bitnset(k_top-2,bm) || prev_keytop->label==NULL) ? 0 :
  514.          strlen(prev_keytop->label)) +
  515.          1 + ((!bitnset(k_top,bm) || keytop->name == NULL) ? 0 : 
  516.            (ufk ? (i >= 5 ? 3 : 2) : strlen(keytop->name)));
  517.     top_column = max(prev_end_top, first + spacing[i]);
  518.     dprint(9, (debugfile,
  519.            "prev_col: %d, prev_end:%d, top_column:%d spacing:%d\n",
  520.            prev_keytop->column, prev_end_top, top_column,
  521.            spacing[i]));
  522.     prev_end_bot = 
  523.          prev_keybot->column +
  524.          ((!bitnset(k_bot-2,bm) || prev_keybot->name  == NULL) ? 0 :
  525.           (ufk ? (i-1 >= 4 ? 3 : 2) :strlen(prev_keybot->name))) +
  526.          1 + ((!bitnset(k_bot-2,bm) || prev_keybot->label==NULL) ? 0 :
  527.          strlen(prev_keybot->label)) +
  528.          1 + ((!bitnset(k_bot,bm) || keybot->name  == NULL) ? 0 :
  529.           (ufk ? (i >= 4 ? 3 : 2) :strlen(keybot->name)));
  530.     bot_column = max(prev_end_bot, first + spacing[i]);
  531.  
  532.         keytop->column = max(bot_column, top_column) -
  533.                           ((!bitnset(k_top,bm) || keytop->name == NULL) ? 0 : 
  534.                             (ufk ? (i >= 5 ? 3 : 2) : strlen(keytop->name)));
  535.         keybot->column = max(bot_column, top_column) -
  536.                           ((!bitnset(k_bot,bm) || keybot->name  == NULL) ? 0 :
  537.                             (ufk ? (i >= 4 ? 3 : 2) : strlen(keybot->name)));
  538.         prev_keytop = keytop;
  539.         prev_keybot = keybot;
  540.     }
  541. }
  542.  
  543.  
  544. /*
  545.  * Draw the key menu at bottom of screen
  546.  *
  547.  * Args:  km     -- key_menu structure
  548.  *        bitmap -- which fields are active
  549.  *        width  -- the screen width to format it at
  550.  *      row    -- where to put it
  551.  *      column -- where to put it
  552.  *        what   -- this is an enum telling us whether to display the
  553.  *            first menu (first set of 12 keys), or to display the same
  554.  *            one we displayed last time, or to display a particular
  555.  *            one (which), or to display the next one.
  556.  *      which  -- the keys to display if (what == AParticularTwelve)
  557.  *
  558.  * Fields are inactive if *either* the corresponding bitmap entry is 0 *or*
  559.  * the actual entry in the key_menu is NULL.  Therefore, it is sometimes
  560.  * useful to just turn on all the bits in a bitmap and let the NULLs take
  561.  * care of it.  On the other hand, the bitmap gives a convenient method
  562.  * for turning some keys on or off dynamically or due to options.
  563.  * Both methods are used about equally.
  564.  *
  565.  * Also saves the state for a possible redraw later.
  566.  *
  567.  * Row should usually be a negative number.  If row is 0, the menu is not
  568.  * drawn.
  569.  */
  570. void
  571. draw_keymenu(km, bitmap, width, row, column, what, which)
  572. struct key_menu *km;
  573. bitmap_t     bitmap;
  574. int             width;
  575. int             row,
  576.                  column;
  577. OtherMenu        what;
  578. int             which;
  579. {
  580. #ifdef _WINDOWS
  581.     configure_menu_items (km, bitmap);
  582. #endif
  583.     if(row == 0)
  584.       return;
  585.  
  586.     format_keymenu(km, bitmap, width, what, which);
  587.     if(km_state.blanked)
  588.       keymenu_is_dirty = 1;
  589.  
  590.     output_keymenu(km, bitmap, row, column);
  591.  
  592.     /*--- save state for a possible redraw ---*/
  593.     km_state.km         = km;
  594.     km_state.row        = row;
  595.     km_state.column     = column;
  596.     memcpy(km_state.bitmap, bitmap, BM_SIZE);
  597.     km_state.blanked    = 0;
  598. }
  599.  
  600.  
  601. void
  602. redraw_keymenu()
  603. {
  604.     if(km_state.blanked)
  605.       blank_keymenu(km_state.row, km_state.column);
  606.     else
  607.       draw_keymenu(km_state.km, km_state.bitmap,
  608.            ps_global->ttyo->screen_cols,
  609.            km_state.row, km_state.column, SameTwelve, 0);
  610. }
  611.     
  612.  
  613. /*
  614.  * some useful macros...
  615.  */
  616. #define    MS_DEL            (0x01)
  617. #define    MS_NEW            (0x02)
  618. #define    MS_ANS            (0x04)
  619.  
  620. #define    STATUS_BITS(X)    (!(X) ? 0                          \
  621.                : (X)->deleted ? MS_DEL                  \
  622.                  : (X)->answered ? MS_ANS                  \
  623.                    : (as.stream                      \
  624.                   && (ps_global->unseen_in_view              \
  625.                       || (!(X)->seen                  \
  626.                       && (!IS_NEWS(as.stream)          \
  627.                           || ((X)->recent              \
  628.                           && F_ON(F_FAKE_NEW_IN_NEWS, \
  629.                               ps_global))))))     \
  630.                       ? MS_NEW : 0)
  631.  
  632. #define    BAR_STATUS(X)    (((X) & MS_DEL) ? "DEL"   \
  633.              : ((X) & MS_ANS) ? "ANS"   \
  634.                    : (as.stream              \
  635.                   && (!IS_NEWS(as.stream)   \
  636.                   || F_ON(F_FAKE_NEW_IN_NEWS, ps_global)) \
  637.                   && ((X) & MS_NEW)) ? "NEW" : "   ")
  638.  
  639. #define    BAR_NUMBER(X)
  640.  
  641.  
  642.  
  643. static struct titlebar_state {
  644.     MAILSTREAM    *stream;
  645.     MSGNO_S    *msgmap;
  646.     char    *title,
  647.         *folder_name,
  648.         *context_name;
  649.     long     current_msg,
  650.          current_line,
  651.          total_lines;
  652.     int         msg_state,
  653.          cur_mess_col,
  654.          del_column, 
  655.          percent_column,
  656.          page_column,
  657.          screen_cols;
  658.     enum     {Normal, ReadOnly, Closed} stream_status;
  659.     TitleBarType style;
  660. } as, titlebar_stack;
  661.  
  662.     
  663.  
  664. /*----------------------------------------------------------------------
  665.        Create little string for displaying message status
  666.  
  667.   Args: message_cache  -- pointer to MESSAGECACHE 
  668.  
  669.     Create a string with letters that indicate the status of the message.
  670.   This is a function despite it's current simplicity so we can easily 
  671.   add a few more flags
  672.   ----------------------------------------------------------------------*/
  673. char *
  674. status_string(stream, mc)
  675.      MAILSTREAM   *stream;
  676.      MESSAGECACHE *mc;
  677. {
  678.      static char string[2] = {'\0', '\0'};
  679.  
  680.      if(!mc || ps_global->nr_mode) {
  681.          string[0] = ' ';
  682.          return(string);
  683.      } 
  684.  
  685.      string[0] = (!stream || !IS_NEWS(stream)
  686.           || (mc->recent && F_ON(F_FAKE_NEW_IN_NEWS, ps_global)))
  687.            ? 'N' : ' ';
  688.  
  689.      if(mc->seen)
  690.        string[0] = ' ';
  691.  
  692.      if(mc->answered)
  693.        string[0] = 'A';
  694.  
  695.      if(mc->deleted)
  696.        string[0] = 'D';
  697.  
  698.      return(string);
  699. }
  700.  
  701.  
  702.  
  703. /*--------
  704. ------*/
  705. void
  706. push_titlebar_state()
  707. {
  708.     titlebar_stack     = as;
  709.     as.folder_name     = NULL;    /* erase knowledge of malloc'd data */
  710.     as.context_name    = NULL;
  711. }
  712.  
  713.  
  714.  
  715. /*--------
  716. ------*/
  717. void
  718. pop_titlebar_state()
  719. {
  720.     fs_give((void **)&(as.folder_name)); /* free malloc'd values */
  721.     fs_give((void **)&(as.context_name));
  722.     as = titlebar_stack;
  723. }
  724.  
  725.  
  726.  
  727. /*--------
  728. ------*/
  729. int
  730. digit_count(n)
  731.     long n;
  732. {
  733.     int i;
  734.  
  735.     return((n > 9)
  736.          ? (1 + (((i = digit_count(n / 10L)) == 3 || i == 7) ? i+1 : i))
  737.          : 1);
  738. }
  739.  
  740.  
  741. static int titlebar_is_dirty = 1;
  742.  
  743. void
  744. mark_titlebar_dirty()
  745. {
  746.     titlebar_is_dirty = 1;
  747. }
  748.  
  749. /*----------------------------------------------------------------------
  750.       Sets up style and contents of current titlebar line
  751.  
  752.     Args: title -- The title that appears in the center of the line
  753.           display_on_screen -- flag whether to display on screen or generate
  754.                                 string
  755.           style  -- The format/style of the titlebar line
  756.       msgmap -- MSGNO_S * to selected message map
  757.           current_pl -- The current page or line number
  758.           total_pl   -- The total line or page count
  759.  
  760.   Set the contents of the acnhor line. It's called an acnhor line
  761. because it's always present and titlebars the user. This accesses a
  762. number of global variables, but doesn't change any. There are 4
  763. different styles of the titlebar line. First three parts are put
  764. together and then the three parts are put together adjusting the
  765. spacing to make it look nice. Finally column numbers and lengths of
  766. various fields are saved to make updating parts of it more efficient.
  767.  
  768. It's OK to call this withy a bogus current message - it is only used
  769. to look up status of current message 
  770.  
  771. Formats only change the right section (part3).
  772.   FolderName:      "<folder>"  xx Messages
  773.   MessageNumber:   "<folder>" message x,xxx of x,xxx XXX
  774.   TextPercent:     line xxx of xxx  xx%
  775.   MsgTextPercent:  "<folder>" message x,xxx of x,xxx  xx% XXX
  776.   FileTextPercent: "<filename>" line xxx of xxx  xx%
  777.  
  778. Several strings and column numbers are saved so later updates to the status 
  779. line for changes in message number or percentage can be done efficiently.
  780. This code is some what complex, and might benefit from some improvements.
  781.  ----*/
  782.  
  783. char *
  784. set_titlebar(title, stream, cntxt, folder, msgmap, display_on_screen, style,
  785.          current_pl, total_pl)
  786.      char        *title;
  787.      MAILSTREAM  *stream;
  788.      CONTEXT_S   *cntxt;
  789.      char        *folder;
  790.      MSGNO_S     *msgmap;
  791.      TitleBarType style;
  792.      int          display_on_screen;
  793.      long      current_pl, total_pl;
  794. {
  795.     char          *tb;
  796.     MESSAGECACHE  *mc = NULL;
  797.     int            start_col;
  798.  
  799.     dprint(9, (debugfile, "set_titlebar - style: %d  current message cnt:%ld",
  800.            style, mn_total_cur(msgmap)));
  801.     dprint(9, (debugfile, "  current_pl: %ld  total_pl: %ld\n", 
  802.            current_pl, total_pl));
  803.  
  804.     as.current_msg   = max(0, mn_get_cur(msgmap));
  805.     as.msgmap         = msgmap;
  806.     as.style         = style;
  807.     as.title         = title;
  808.     as.stream         = stream;
  809.     as.stream_status = (!as.stream || (as.stream == ps_global->mail_stream
  810.                        && ps_global->dead_stream))
  811.              ? Closed : as.stream->rdonly ? ReadOnly : Normal;
  812.  
  813.     if(as.folder_name)
  814.       fs_give((void **)&as.folder_name);
  815.  
  816.     as.folder_name = cpystr(pretty_fn(folder));
  817.  
  818.     if(as.context_name)
  819.       fs_give((void **)&as.context_name);
  820.  
  821.     /*
  822.      * Handle setting up the context if appropriate.
  823.      */
  824.     if(cntxt && context_isambig(folder) && ps_global->context_list->next
  825.        && strucmp(as.folder_name, ps_global->inbox_name)){
  826.     /*
  827.      * if there's more than one context and the current folder
  828.      * is in it (ambiguous name), set the context name...
  829.      */
  830.     as.context_name = cpystr(cntxt->label[0]);
  831.     }
  832.     else
  833.       as.context_name = cpystr("");
  834.  
  835.     if(mn_get_total(msgmap) < 1L)
  836.       mn_set_cur(msgmap, 0L);        /* BUG Don't like this */
  837.  
  838.     if(as.stream && style != FolderName && mn_get_cur(msgmap) > 0L) {
  839.         (void)mail_fetchstructure(as.stream,
  840.                   mn_m2raw(msgmap, as.current_msg), NULL);
  841.         mc  = mail_elt(as.stream, mn_m2raw(msgmap, as.current_msg));
  842.     }
  843.     
  844.     switch(style) {
  845.       case FolderName:
  846.     break;
  847.  
  848.       case MessageNumber:
  849.         as.total_lines = total_pl;
  850.         as.current_line = current_pl;
  851.         as.msg_state = STATUS_BITS(mc);
  852.     break;
  853.  
  854.       case TextPercent:
  855.       case MsgTextPercent:
  856.       case FileTextPercent :
  857.         as.total_lines = total_pl;
  858.         as.current_line = current_pl;
  859.         as.msg_state = STATUS_BITS(mc);
  860.     break; 
  861.     }
  862.  
  863.     tb = format_titlebar(&start_col);
  864.     if(display_on_screen){
  865.         StartInverse();
  866.         PutLine0(0, start_col, tb+start_col);
  867.         EndInverse();
  868.         fflush(stdout);
  869.     }
  870.  
  871.     return(tb);
  872. }
  873.  
  874. void 
  875. redraw_titlebar()
  876. {
  877.     int   start_col;
  878.     char *tb;
  879.  
  880.     StartInverse();
  881.     tb = format_titlebar(&start_col);
  882.     PutLine0(0, start_col, tb+start_col);
  883.     EndInverse();
  884.     fflush(stdout);
  885. }
  886.      
  887.  
  888.  
  889.  
  890. /*----------------------------------------------------------------------
  891.       Redraw or draw the top line, the title bar 
  892.  
  893.  The titlebar has Four fields:
  894.      1) "Version" of fixed length and is always positioned two spaces 
  895.         in from left display edge.
  896.      2) "Location" which is fixed for each style of titlebar and
  897.         is positioned two spaces from the right display edge
  898.      3) "Title" which is of fixed length, and is centered if
  899.         there's space
  900.      4) "Folder" whose existance depends on style and which can
  901.         have it's length adjusted (within limits) so it will
  902.         equally share the space between 1) and 2) with the 
  903.         "Title".  The rule for existance is that in the
  904.         space between 1) and 2) there must be two spaces between
  905.         3) and 4) AND at least 50% of 4) must be displayed.
  906.  
  907.  
  908.  The rules for dislay are:
  909.      a) Show at least some portion of 3)
  910.      b) If no room for 1) and 3), 3)
  911.      c) If no room for 1), 2) and 3), show 1) and 2)
  912.      d) If no room for all and > 50% of 4), show 1), 2), and 3)
  913.      e) show 1), 2) 3), and some portion of 4)
  914.  
  915.    Returns - Formatted title bar 
  916.        - Start_col will point to column to start printing in on return.
  917.  ----*/
  918. char *
  919. format_titlebar(start_col)
  920.     int *start_col;
  921. {
  922.     static  char titlebar_line[MAX_SCREEN_COLS+1];
  923.     char    version[50], fold_tmp[MAXPATH],
  924.            *loc_label, *ss_string;
  925.     int     sc, tit_len, ver_len, loc_len, fold_len, num_len, ss_len, 
  926.             is_context;
  927.  
  928.     if(start_col)
  929.       *start_col = 0; /* default */
  930.  
  931.     /* blank the line */
  932.     memset((void *)titlebar_line, ' ', MAX_SCREEN_COLS*sizeof(char));
  933.     sc = min(ps_global->ttyo->screen_cols, MAX_SCREEN_COLS);
  934.     titlebar_line[sc] = '\0';
  935.  
  936.     /* initialize */
  937.     as.del_column     = -1;
  938.     as.cur_mess_col   = -1;
  939.     as.percent_column = -1;
  940.     as.page_column    = -1;
  941.     is_context        = strlen(as.context_name);
  942.     sprintf(version, "PINE %s", pine_version); 
  943.     ss_string         = as.stream_status == Closed ? "(CLOSED)" :
  944.                         (as.stream_status == ReadOnly
  945.              && !IS_NEWS(as.stream))
  946.                            ? "(READONLY)" : "";
  947.     ss_len            = strlen(ss_string);
  948.  
  949.     tit_len = strlen(as.title);        /* fixed title field width   */
  950.     ver_len = strlen(version);        /* fixed version field width */
  951.  
  952.     /* if only room for title we can get out early... */
  953.     if(tit_len >= sc || (tit_len + ver_len + 6) > sc){
  954.     int i = max(0, sc - tit_len)/2;
  955.     strncpy(titlebar_line + i, as.title, min(sc, tit_len));
  956.     titlebar_is_dirty = 0;
  957.     return(titlebar_line);
  958.     }
  959.  
  960.     /* 
  961.      * set location field's length and value based on requested style 
  962.      */
  963.     loc_label = (is_context) ? "Msg" : "Message";
  964.     loc_len   = strlen(loc_label);
  965.     switch(as.style){
  966.       case FolderName :            /* "x,xxx <loc_label>s" */
  967.     loc_len += digit_count(mn_get_total(as.msgmap)) + 3;
  968.     sprintf(tmp_20k_buf, "%s %s%s", comatose(mn_get_total(as.msgmap)),
  969.         loc_label, plural(mn_get_total(as.msgmap)));
  970.     break;
  971.       case MessageNumber :               /* "<loc_label> xxx of xxx DEL"  */
  972.     num_len         = digit_count(mn_get_total(as.msgmap));
  973.     loc_len        += (2 * num_len) + 9;    /* add spaces and "DEL" */
  974.     as.cur_mess_col  = sc - (2 * num_len) - 10;
  975.     as.del_column    = as.cur_mess_col + num_len 
  976.                 + digit_count(as.current_msg) + 5;
  977.     sprintf(tmp_20k_buf, "%s %s of %s %s", loc_label,
  978.         strcpy(tmp_20k_buf + 1000, comatose(as.current_msg)),
  979.         strcpy(tmp_20k_buf + 1500, comatose(mn_get_total(as.msgmap))),
  980.         BAR_STATUS(as.msg_state));
  981.     break;
  982.       case MsgTextPercent :        /* "<loc_label> xxx of xxx xxx% DEL" */
  983.     num_len           = digit_count(mn_get_total(as.msgmap));
  984.     loc_len          += (2 * num_len) + 13; /* add spaces, %, and "DEL" */
  985.     as.cur_mess_col    = sc - 16 - (2 * num_len);
  986.     as.percent_column  = as.cur_mess_col + num_len
  987.                   + digit_count(as.current_msg) + 7;
  988.     as.del_column      = as.percent_column + 4;
  989.     sprintf(tmp_20k_buf, "%s %s of %s %s %s", loc_label, 
  990.         strcpy(tmp_20k_buf + 1000, comatose(as.current_msg)),
  991.         strcpy(tmp_20k_buf + 1500, comatose(mn_get_total(as.msgmap))),
  992.         percentage(as.current_line, as.total_lines, 1),
  993.         BAR_STATUS(as.msg_state));
  994.     break;
  995.       case TextPercent :
  996.     /* NOTE: no fold_tmp setup below for TextPercent style */
  997.       case FileTextPercent :
  998.     as.page_column = sc - (14 + 2*(num_len = digit_count(as.total_lines)));
  999.     loc_len        = 17 + 2*num_len;
  1000.     sprintf(tmp_20k_buf, "Line %*ld of %*ld %s    ",
  1001.         num_len, as.current_line, 
  1002.         num_len, as.total_lines,
  1003.         percentage(as.current_line, as.total_lines, 1));
  1004.     break;
  1005.     }
  1006.  
  1007.     /* at least the version will fit */
  1008.     strncpy(titlebar_line + 2, version, ver_len);
  1009.     if(!titlebar_is_dirty && start_col)
  1010.       *start_col = 2 + ver_len;
  1011.  
  1012.     titlebar_is_dirty = 0;
  1013.  
  1014.     /* if no room for location string, bail early? */
  1015.     if(ver_len + tit_len + loc_len + 10 > sc){
  1016.     strncpy((titlebar_line + sc) - (tit_len + 2), as.title, tit_len);
  1017.         as.del_column = as.cur_mess_col = as.percent_column
  1018.       = as.page_column = -1;
  1019.     return(titlebar_line);        /* put title and leave */
  1020.     }
  1021.  
  1022.     /* figure folder_length and what's to be displayed */
  1023.     fold_tmp[0] = '\0';
  1024.     if(as.style == FileTextPercent || as.style == TextPercent){
  1025.     if(as.style == FileTextPercent && !ps_global->anonymous){
  1026.         char *fmt    = "File: %s%s";
  1027.         int   avail  = sc - (ver_len + tit_len + loc_len + 10);
  1028.         fold_len     = strlen(as.folder_name);
  1029.         if(fold_len + 6 < avail)     /* all of folder fit? */
  1030.           sprintf(fold_tmp, fmt, "", as.folder_name);
  1031.         else if((fold_len/2) + 9 < avail)
  1032.           sprintf(fold_tmp, fmt, "...",
  1033.               as.folder_name + fold_len - (avail - 9));
  1034.     }
  1035.     /* else leave folder/file name blank */
  1036.     }
  1037.     else{
  1038.     int ct_len,
  1039.         avail  = sc - (ver_len + tit_len + loc_len + 10);
  1040.     fold_len   = strlen(as.folder_name);
  1041.     
  1042.     if(is_context
  1043.       && as.stream_status != Closed
  1044.       && (ct_len = strlen(as.context_name))){
  1045.         char *fmt;
  1046.         int  extra;
  1047.  
  1048.         fmt = "<%*.*s> %s%s"; extra = 3;
  1049.  
  1050.         /*
  1051.          * below are other formats we'd considered
  1052.          *
  1053.          * fmt = "%s - %s%s"; extra = 3;
  1054.          * fmt = "%s[%s%s]"; extra = 2;
  1055.          * fmt = "<%s>%s%s"; extra = 2;
  1056.          * fmt = "%s: %s%s"; extra = 2;
  1057.          */
  1058.         if(ct_len + fold_len + ss_len + extra < avail)
  1059.           sprintf(fold_tmp, fmt, ct_len, ct_len, as.context_name,
  1060.               as.folder_name, ss_string);
  1061.         else if((ct_len/2) + fold_len + ss_len + extra < avail)
  1062.           sprintf(fold_tmp, fmt,
  1063.               ct_len - (ct_len-(avail-(fold_len+ss_len+extra))),
  1064.               ct_len - (ct_len-(avail-(fold_len+ss_len+extra))),
  1065.               as.context_name,
  1066.               as.folder_name, ss_string);
  1067.         else if((ct_len/2) + (fold_len/2) + ss_len + extra < avail)
  1068.           sprintf(fold_tmp, fmt, (ct_len/2), (ct_len/2), as.context_name,
  1069.            as.folder_name+(fold_len-(avail-((ct_len/2)+ss_len+extra))),
  1070.               ss_string);
  1071.     }
  1072.     else{
  1073.         char *fmt = "Folder: %s%s";
  1074.         if(fold_len + ss_len + 8 < avail)     /* all of folder fit? */
  1075.           sprintf(fold_tmp, fmt, as.folder_name, ss_string);
  1076.         else if((fold_len/2) + ss_len + 8 < avail)
  1077.           sprintf(fold_tmp, fmt, 
  1078.               as.folder_name + fold_len - (avail - (8 + ss_len)),
  1079.               ss_string);
  1080.     }
  1081.     }
  1082.     
  1083.     /* write title, location and, optionally, the folder name */
  1084.     fold_len = strlen(fold_tmp);
  1085.     strncpy(titlebar_line + ver_len + 5, as.title, tit_len);
  1086.     strncpy((titlebar_line + sc) - (loc_len + 2), tmp_20k_buf, 
  1087.         strlen(tmp_20k_buf));
  1088.     if(fold_len)
  1089.       strncpy((titlebar_line + sc) - (loc_len + fold_len + 4), fold_tmp,
  1090.           fold_len);
  1091.  
  1092.     return(titlebar_line);
  1093. }
  1094.  
  1095.  
  1096. /*----------------------------------------------------------------------
  1097.     Update the titlebar line if the message number changed
  1098.  
  1099.    Args: None, uses state setup on previous call to set_titlebar.
  1100.  
  1101. This is a bit messy because the length of the number displayed might 
  1102. change which repositions everything after it, so we adjust all the saved 
  1103. columns and shorten tail, the string holding the rest of the line.
  1104.   ----*/
  1105.  
  1106. void
  1107. update_titlebar_message()
  1108. {
  1109.     int delta;
  1110.  
  1111.     if(as.cur_mess_col < 0)
  1112.       return;
  1113.  
  1114.     delta = digit_count(mn_get_cur(as.msgmap)) - digit_count(as.current_msg);
  1115.  
  1116.     StartInverse();
  1117.     if(delta) {
  1118.     as.current_msg = mn_get_cur(as.msgmap);
  1119.  
  1120.         if(as.style == MsgTextPercent){
  1121.             PutLine5(0, as.cur_mess_col, "%s of %s %s %s%s",
  1122.              strcpy(tmp_20k_buf + 1000, comatose(as.current_msg)),
  1123.              strcpy(tmp_20k_buf + 1500,
  1124.                 comatose(mn_get_total(as.msgmap))),
  1125.                      percentage(as.current_line, as.total_lines, 0),
  1126.                      BAR_STATUS(as.msg_state),
  1127.                      repeat_char(max(0, -delta), ' '));
  1128.         as.del_column     += delta;
  1129.         as.percent_column += delta;
  1130.         } else {
  1131.             PutLine4(0, as.cur_mess_col, "%s of %s %s%s",
  1132.              strcpy(tmp_20k_buf + 1000, comatose(as.current_msg)),
  1133.              strcpy(tmp_20k_buf + 1500,
  1134.                 comatose(mn_get_total(as.msgmap))),
  1135.              BAR_STATUS(as.msg_state),
  1136.                      repeat_char(max(0, -delta),' '));
  1137.         as.del_column += delta;
  1138.         }
  1139.     }
  1140.     else if(mn_get_cur(as.msgmap) != as.current_msg){
  1141.     as.current_msg = mn_get_cur(as.msgmap);
  1142.     PutLine0(0, as.cur_mess_col, comatose(as.current_msg));
  1143.     }
  1144.  
  1145.     EndInverse();
  1146.     fflush(stdout);
  1147. }
  1148.  
  1149.  
  1150.  
  1151. /*----------------------------------------------------------------------
  1152.     Update titlebar line's message status field ("DEL", "NEW", etc)
  1153.  
  1154.   Args:  None, operates on state set during most recent set_titlebar call
  1155.  
  1156.   ---*/
  1157. void
  1158. update_titlebar_status()
  1159. {
  1160.     MESSAGECACHE *mc;
  1161.     
  1162.     if(!as.stream || as.current_msg < 0L || as.del_column < 0)
  1163.       return;
  1164.  
  1165.     mc = mail_elt(as.stream, mn_m2raw(as.msgmap, as.current_msg));
  1166.  
  1167.     if(mc->deleted){            /* deleted takes precedence */
  1168.     if(as.msg_state & MS_DEL)
  1169.       return;
  1170.     }
  1171.     else if(mc->answered){        /* then answered */
  1172.     if(as.msg_state & MS_ANS)
  1173.       return;
  1174.     }
  1175.     else if(!mc->seen && as.stream
  1176.         && (!IS_NEWS(as.stream)
  1177.         || (mc->recent && F_ON(F_FAKE_NEW_IN_NEWS, ps_global)))){
  1178.     if(as.msg_state & MS_NEW)    /* then seen */
  1179.       return;
  1180.     }
  1181.     else if(IS_NEWS(as.stream) && F_ON(F_FAKE_NEW_IN_NEWS, ps_global)){
  1182.     }
  1183.     else if(as.msg_state == 0)        /* nothing to change... */
  1184.       return;
  1185.  
  1186.     as.msg_state = STATUS_BITS(mc);
  1187.     StartInverse();
  1188.     PutLine0(0, as.del_column, BAR_STATUS(as.msg_state));
  1189.     EndInverse();
  1190.     fflush(stdout);
  1191. }
  1192.  
  1193.  
  1194.  
  1195. /*---------------------------------------------------------------------- 
  1196.     Update the percentage shown in the titlebar line
  1197.  
  1198.   Args: new_line_number -- line number to calculate new percentage
  1199.    
  1200.   ----*/
  1201.  
  1202. void
  1203. update_titlebar_percent(line)
  1204.     long line;
  1205. {
  1206.     if(as.percent_column < 0)
  1207.       return;
  1208.  
  1209.     StartInverse();
  1210.     PutLine0(0, as.percent_column,
  1211.          percentage(as.current_line = line, as.total_lines, 0));
  1212.     EndInverse();
  1213.     fflush(stdout);
  1214. }
  1215.  
  1216.  
  1217.  
  1218. /*---------------------------------------------------------------------- 
  1219.     Update the percentage AND line number shown in the titlebar line
  1220.  
  1221.   Args: new_line_number -- line number to calculate new percentage
  1222.    
  1223.   ----*/
  1224.  
  1225. void
  1226. update_titlebar_lpercent(new_line_number)
  1227.     long new_line_number;
  1228. {
  1229.     if(as.page_column < 0 || new_line_number == as.current_line)
  1230.       return;
  1231.  
  1232.     as.current_line = new_line_number;
  1233.  
  1234.     sprintf(tmp_20k_buf, "%*ld of %*ld %s    ",
  1235.         digit_count(as.total_lines), as.current_line, 
  1236.         digit_count(as.total_lines), as.total_lines,
  1237.         percentage(as.current_line, as.total_lines, 0));
  1238.     StartInverse();
  1239.     PutLine0(0, as.page_column, tmp_20k_buf);
  1240.     EndInverse();
  1241.     fflush(stdout);
  1242. }
  1243.  
  1244.  
  1245.  
  1246. /*----------------------------------------------------------------------
  1247.     Return static buf containing portion of lines displayed
  1248.  
  1249.   Args:  part -- how much so far
  1250.      total -- how many total
  1251.  
  1252.   ---*/
  1253. char *
  1254. percentage(part, total, suppress_top)
  1255.     long part, total;
  1256.     int  suppress_top;
  1257. {
  1258.     static char percent[4];
  1259.  
  1260.     if(total == 0L || (total <= ps_global->ttyo->screen_rows
  1261.                  - HEADER_ROWS(ps_global)
  1262.                  - FOOTER_ROWS(ps_global)))
  1263.       strcpy(percent, "ALL");
  1264.     else if(!suppress_top && part <= ps_global->ttyo->screen_rows
  1265.                       - HEADER_ROWS(ps_global)
  1266.                       - FOOTER_ROWS(ps_global))
  1267.       strcpy(percent, "TOP");
  1268.     else if(part >= total)
  1269.       strcpy(percent, "END");
  1270.     else
  1271.       sprintf(percent, "%2ld%%", (100L * part)/total);
  1272.  
  1273.     return(percent);
  1274. }
  1275.  
  1276.  
  1277.  
  1278.  
  1279. /*
  1280.  * end_titlebar - free resources associated with titlebar state struct
  1281.  */
  1282. void
  1283. end_titlebar()
  1284. {
  1285.     if(as.folder_name)
  1286.       fs_give((void **)&as.folder_name);
  1287.  
  1288.     if(as.context_name)
  1289.       fs_give((void **)&as.context_name);
  1290. }
  1291.  
  1292.  
  1293. /*
  1294.  * end_keymenu - free resources associated with keymenu display cache
  1295.  */
  1296. void
  1297. end_keymenu()
  1298. {
  1299.     int i;
  1300.  
  1301.     for(i = 0; i < 12; i++){
  1302.     if(last_time_buf[i].name)
  1303.       fs_give((void **)&last_time_buf[i].name);
  1304.  
  1305.     if(last_time_buf[i].label)
  1306.       fs_give((void **)&last_time_buf[i].label);
  1307.     }
  1308. }
  1309.  
  1310.  
  1311. /*
  1312.  * Save the bits from the which'th set of twelve bits in from into to.
  1313.  * An assumption is that bitmap_t's are 48 bits longs.  (There must be a
  1314.  * clever macro way to do this.)
  1315.  */
  1316. void
  1317. savebits(to, from, which)
  1318. bitmap_t to,
  1319.      from;
  1320. int     which;
  1321. {
  1322.     unsigned char c1, c2;
  1323.     int x;
  1324.  
  1325.     switch(which){
  1326.       case 0:
  1327.       case 2:
  1328.     x       =  which == 0 ? 0 : 3;
  1329.     to[x]   =  from[x];
  1330.     c1      =  from[x+1]  &  0x0f;
  1331.     c2      =  to[x+1]    &  0xf0;
  1332.     to[x+1] =  c1         |  c2;
  1333.     break;
  1334.  
  1335.       case 1:
  1336.       case 3:
  1337.     x       =  which == 1 ? 1 : 4;
  1338.     c1      =  to[x]    &  0x0f;
  1339.     c2      =  from[x]  &  0xf0;
  1340.     to[x]   =  c1       |  c2;
  1341.     to[x+1] =  from[x+1];
  1342.     break;
  1343.  
  1344.       default:
  1345.     panic("Can't happen in savebits()\n");
  1346.     break;
  1347.     }
  1348. }
  1349.  
  1350.  
  1351. /*
  1352.  * Returns 1 if the which'th set of twelve bits in bm1 is the same as the
  1353.  * which'th set of twelve bits in bm2, else 0.
  1354.  */
  1355. int
  1356. equalbits(bm1, bm2, which)
  1357.     bitmap_t bm1, bm2;
  1358.     int         which;
  1359. {
  1360.     int x;
  1361.  
  1362.     switch(which){
  1363.       case 0:
  1364.       case 2:
  1365.     x = which == 0 ? 0 : 3;
  1366.     return ((bm1[x] == bm2[x]) && ((bm1[x+1] & 0x0f) == (bm2[x+1] & 0x0f)));
  1367.  
  1368.       case 1:
  1369.       case 3:
  1370.     x = which == 1 ? 1 : 4;
  1371.     return (((bm1[x] & 0xf0) == (bm2[x] & 0xf0)) && (bm1[x+1] == bm2[x+1]));
  1372.  
  1373.       default:
  1374.     panic("Can't happen in equalbits()\n");
  1375.     }
  1376. }
  1377.